# Появление ООП — реакция на кризис программного обеспечения

Основано на [статье](http://artlib.osu.ru/Docs/piter/bookchap/978594723842.html).

Объектно-ориентированное программирование (ООП) — это технология, возникшая как реакция на очередную фазу кризиса программного обеспечения, когда методы структурного программирования уже не позволяли справляться с растущей сложностью промышленного программного продукта. Следствия — срыв сроков проектов, перерасход бюджета, урезанная функциональность и множество ошибок.

Существенная черта промышленной программы — ее сложность: один разработчик не в состоянии охватить все аспекты системы, поэтому в ее создании участвует целый коллектив. Следовательно, к первичной сложности самой задачи, вытекающей из предметной области, добавляется управление процессом разработки с учетом необходимости координации действий в команде разработчиков.

Так как сложные системы разрабатываются в расчете на длительную эксплуатацию, то появляются еще две проблемы: сопровождение системы (устранение обнаруженных ошибок) и ее модификация, поскольку у заказчика постоянно появляются новые требования и пожелания. Иногда затраты на сопровождение и модификацию сопоставимы с затратами на собственно разработку системы.

Способ управления сложными системами был известен еще в древности — divide et impera (разделяй и властвуй). То есть выход — в декомпозиции системы на все меньшие и меньшие подсистемы, каждую из которых можно совершенствовать независимо. Здесь вы, наверное, вспомните о методе нисходящего проектирования, которым мы активно пользовались в первой книге практикума. Но если в рамках структурного подхода декомпозиция понимается как разбиение алгоритма, когда каждый из модулей системы выполняет один из этапов общего процесса, то ООП предлагает совершенно другой подход.

Суть его в том, что в качестве критерия декомпозиции принимается принадлежность ее элементов к различным абстракциям проблемной области. Откуда же берутся эти абстракции? Исключительно из головы программиста, который, анализируя предметную область, вычленяет из нее отдельные объекты. Для каждого из этих объектов определяются свойства, существенные для решения задачи. Затем каждому реальному объекту предметной области ставится в соответствие программный объект.

Почему объектно-ориентированная декомпозиция оказалась более эффективным средством борьбы со сложностью процессов проектирования и сопровождения программных систем, чем функциональная декомпозиция? Тому есть много причин. Чтобы в них разобраться, рассмотрим критерии качества проекта, связанные с его декомпозицией.

## Критерии качества декомпозиции проекта

Со сложностью приложения трудно что-либо сделать — она определяется целью создания программы. А вот сложность реализации можно попытаться контролировать. Первый вопрос, возникающий при декомпозиции: на какие компоненты (модули, функции, классы) нужно разбить программу? Очевидно, что с ростом числа компонентов сложность программы растет, поскольку необходима кооперация, координация и коммуникация между компонентами. Особенно негативны последствия неоправданного разбиения на компоненты, когда оказываются разделенными действия, по сути тесно связанные между собой.

Вторая проблема связана с организацией взаимодействия между компонентами. Взаимодействие упрощается и его легче взять под контроль, если каждый компонент рассматривается как некий "черный ящик", внутреннее устройство которого неизвестно, но известны выполняемые им функции, а также "входы" и "выходы" этого ящика. Вход компонента позволяет ввести в него значение некоторой входной переменной, а выход — получить значение некоторой выходной переменной. В программировании совокупность входов и выходов черного ящика определяет интерфейс компонента. Интерфейс реализуется как набор некоторых функций (или запросов к компоненту), вызывая которые клиент либо получает какую-то информацию, либо меняет состояние компонента.

Модное нынче словечко "клиент" означает просто-напросто компонент, которому понадобились услуги другого компонента, исполняющего в этом случае роль сервера. Взаимоотношение клиент/сервер на самом деле очень старо и использовалось уже в рамках структурного программирования, когда функция-клиент пользовалась услугами функции-сервера путем ее вызова.

Подытожим сказанное о проблемах разбиения программы на компоненты и организации их взаимодействия. Для оценки качества программного проекта нужно учитывать, кроме всех прочих, следующие два показателя:

- Сцепление (cohesion) внутри компонента — показатель, характеризующий степень взаимосвязи отдельных его частей. Простой пример: если внутри компонента решаются две подзадачи, которые легко можно разделить, то компонент обладает слабым (плохим) сцеплением.
- Связанность (coupling) между компонентами — показатель, описывающий интерфейс между компонентом-клиентом и компонентом-сервером. Общее число входов и выходов сервера есть мера связанности. Чем меньше связанность между двумя компонентами, тем проще понять и отслеживать в будущем их взаимодействие. А так как в больших проектах эти компоненты часто разрабатываются разными людьми, то очень важно уменьшать связанность между компонентами.

Заметим, что описанные показатели, конечно, имеют относительный характер, и пользоваться ими следует благоразумно. Например, фанатичное следование первому показателю (сильное сцепление) может привести к дроблению проекта на очень большое количество мелких функций, и сопровождающий программист вряд ли помянет вас добрым словом.

Почему в случае функциональной декомпозиции трудно достичь слабой связанности между компонентами? Дело в том, что интерфейс между компонентами в таком проекте реализуется либо через глобальные переменные, либо через механизм формальных/фактических параметров. В сложной программной системе, реализованной в рамках структурной парадигмы, практически невозможно обойтись без связи через глобальные структуры данных, а это означает, что фактически любая функция в случае ошибки может испортить эти данные. Подобные ошибки очень трудно локализуются в процессе отладки. Добавьте к этому головную боль для сопровождающего программиста, который должен помнить десятки (если не сотни) обращений к общим данным из разных частей проекта. Соответственно, модификация существующего проекта в связи с новыми требованиями заказчика также потребует очень большой работы, так как возникнет необходимость проверить влияние внесенных изменений практически на все компоненты проекта.

Другой проблемой в проектах с функциональной декомпозицией было "проклятие" общего глобального пространства имен. Члены команды, работающей над проектом, должны были тратить немалые усилия по согласованию применяемых имен для своих функций, чтобы они были уникальными в рамках всего проекта.

## Что принесло с собой ООП

Первым бросающимся в глаза отличием ООП от структурного программирования является использование классов. Класс — это тип, определяемый программистом, в котором объединяются структуры данных и функции их обработки. Конкретные переменные типа данных "класс" называются экземплярами класса, или объектами. Программы, разрабатываемые на основе концепций ООП, реализуют алгоритмы, описывающие взаимодействие между объектами.

Класс содержит константы и переменные, называемые полями, а также выполняемые над ними операции и функции. Функции класса называются методами. Предполагается, что доступ к полям класса возможен только через вызов соответствующих методов. Поля и методы являются элементами, или членами класса.

Эффективным механизмом ослабления связанности между компонентами в случае объектно-ориентированной декомпозиции является так называемая инкапсуляция.

Инкапсуляция — это ограничение доступа к данным и их объединение с методами, обрабатывающими эти данные. Доступ к отдельным частям класса регулируется с помощью специальных ключевых слов: public (открытая часть), private (закрытая часть) и protected (защищенная часть).

Методы, расположенные в открытой части, формируют интерфейс класса и могут свободно вызываться клиентом через соответствующий объект класса. Доступ к закрытой секции класса возможен только из его собственных методов, а к защищенной — из его собственных методов, а также из методов классов-потомков.

Инкапсуляция повышает надежность программ, предотвращая непреднамеренный ошибочный доступ к полям объекта. Кроме этого, программу легче модифицировать, поскольку при сохранении интерфейса класса можно менять его реализацию, и это не затронет внешний программный код (код клиента).

С понятием инкапсуляции тесно связано понятие сокрытия информации. С другой стороны, понятие сокрытия информации соприкасается с понятием разделения ответственности между клиентом и сервером. Клиент не обязан знать, как реализованы те или иные методы в сервере. Для него достаточно знать, что делает данный метод и как к нему обратиться. При хорошем проектировании имена методов обычно отражают суть выполняемой ими работы, поэтому чтение кода клиента для сопровождающего программиста превращается просто в удовольствие, если не сказать — в наслаждение.

Заметим, что класс одаривает своего программиста-разработчика надежным "укрытием", обеспечивая локальную (в пределах класса) область видимости имен. Теперь можно сократить штат бригады программистов: специалист, отвечающий за согласование имен функций и имен глобальных структур данных между членами бригады, стал не нужен. В разных классах методы, реализующие схожие подзадачи, могут преспокойно иметь одинаковые имена. То же относится и к полям разных классов.

С ООП связаны еще два инструмента, грамотное использование которых повышает качество проектов: наследование классов и полиморфизм.

Наследование — механизм получения нового класса из существующего.

Производный класс создается путем дополнения или изменения существующего класса. Благодаря этому реализуется концепция повторного использования кода. С помощью наследования может быть создана иерархия родственных типов, которые совместно используют код и интерфейсы.

Полиморфизм дает возможность создавать множественные определения для операций и функций. Какое именно определение будет использоваться, зависит от контекста программы. Вы уже знакомы с одной из разновидностей полиморфизма в языке C++ — перегрузкой функций. Программирование с классами предоставляет еще две возможности: перегрузку операций и использование так называемых виртуальных методов. Перегрузка операций позволяет применять для собственных классов те же операции, которые используются для встроенных типов C++. Виртуальные методы обеспечивают возможность выбрать на этапе выполнения нужный метод среди одноименных методов базового и производного классов.

Кроме наследования, классы могут находиться также в отношении агрегации: например, когда в составе одного класса имеются объекты другого класса. Совместное использование наследования, композиции и полиморфизма лежит в основе элегантных проектных решений, обеспечивающих наибольшую простоту модификации программы (эта тема будет рассмотрена на втором семинаре).

Ну и, наконец, отметим, что в реальном проекте, разработанном на базе объектно-ориентированной декомпозиции, находится место и для алгоритмически-ориентированной декомпозиции (например, при реализации сложных методов).

## От структуры — к классу

Прообразом класса в C++ является структура в С. В то же время в C++ структура обрела новые свойства и теперь является частным видом класса, все элементы которого по умолчанию являются открытыми. Со структурой struct в C++ можно делать все, что можно делать с классом. Тем не менее в С++ структуры обычно используют лишь для удобства работы с небольшими наборами данных без какого-либо собственного поведения.
